package com.mybpm.core.definition


import groovy.xml.XmlParser
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service

/**
 * BPMN流程解析服务
 */
@Service
public class ProcessParseBpmnService {
    private static final Logger log = LoggerFactory.getLogger(ProcessParseBpmnService.class)

    public static final List processProperties = ['id', 'name', 'displayName', 'version']
    public static final List startProperties = ['id', 'name', 'displayName', 'actor::camunda:candidateUsers']
    public static final List endProperties = ['id', 'name', 'displayName']
    public static final List nodeProperties = ['id', 'name', 'displayName', 'actor::camunda:candidateUsers']
    public static final List transitionProperties = ['id', 'name', 'displayName', 'to::targetRef', 'toName', 'actor']

    /**
     * 拆分流程定义字符串，并获得流程定义信息<p>
     *
     * @params xml            流程定义
     */
    public Map parse(String xml) {
        Map process = [:]
        groovy.util.Node procDef = new XmlParser(false, false).parseText(xml).'bpmn:process'[0]
//        procDef = procDef.'bpmn:process'
        XmlProcess xmlProcess = new XmlProcess()
        xmlProcess.hashcode = xml.hashCode()                    // 记录当前XML字符串的hashcode
        xmlProcess.timestamp = System.currentTimeMillis()    // 记录当前时间戳
        // TODO 其它属性
        addProps(xmlProcess, procDef, processProperties)
        xmlProcess.id = generateId(xmlProcess.id, xmlProcess.name)
        log.debug("-------parse:{}", xmlProcess.properties)
        process.process = xmlProcess
        parseStart(procDef, process)     // 解析开始环节
        parseEnd(procDef, process)       // 解析结束环节
        parseNode(procDef, process)      // 解析其它环节
        transition2Node(process)         // 关联路径的目标环节
        return process
    }

    /**
     * 解析开始环节
     */
    private void parseStart(groovy.util.Node procDef, Map process) {
        groovy.util.Node startNode = procDef.'bpmn:startEvent'[0]    //限制:开始节点只有一个
        XmlNode start = new XmlNode(type: 'start')
        // TODO 其它属性
        addProps(start, startNode, startProperties)
        start.id = generateId(start.id, start.name)
        process.start = start
        // 路径
        parseTransition(procDef, startNode, start, process)
    }

    /**
     * 解析结束环节
     */
    private void parseEnd(groovy.util.Node procDef, Map process) {
        groovy.util.Node endtNode = procDef.'bpmn:endEvent'[0]    //限制:结束节点只有一个
        XmlNode end = new XmlNode(type: 'end')
        // TODO 其它属性
        addProps(end, endtNode, endProperties)
        end.id = generateId(end.id, end.name)
        process.end = end
    }

    /**
     * 解析环节
     */
    private void parseNode(groovy.util.Node procDef, Map process) {
        process.nodes = [:]
        // 遍历环节
        def tasks = procDef.'*'.findAll { it.name().toLowerCase().indexOf('task') > -1 }
        for (groovy.util.Node n in tasks) {
            XmlNode xmlNode = new XmlNode(type: 'node')
            // TODO 其它属性
            addProps(xmlNode, n, nodeProperties)
            xmlNode.id = generateId(xmlNode.id, xmlNode.name)
            process.nodes[xmlNode.id] = xmlNode
            // 路径
            parseTransition(procDef, n, xmlNode, process)
        }
    }

    /**
     * 解析路径
     */
    private void parseTransition(groovy.util.Node procDef, groovy.util.Node node, XmlNode xmlNode, Map process) {
        // 路径
        xmlNode.transitions = []
        if (!process.containsKey("transitions")) process.transitions = [:]
        for (groovy.util.Node t in node.'bpmn:outgoing') {
            XmlTransition xmlTransition = new XmlTransition()
            groovy.util.Node st = procDef.'bpmn:sequenceFlow'.findAll { it.attribute('id') == t.text().trim() }[0]
            // TODO 其它属性
            addProps(xmlTransition, st, transitionProperties)
            xmlTransition.id = generateId(xmlTransition.id, xmlTransition.name)
            // TODO 路径id不存在时，需要计算
            if (!xmlTransition.id) {
                xmlTransition.id = xmlNode.id + "-" + generateId(xmlTransition.to, xmlTransition.toName)
            }
            xmlTransition.fromNode = xmlNode
            xmlNode.transitions.add(xmlTransition)
            process.transitions[xmlTransition.id] = xmlTransition
        }
    }

    /**
     * 关联路径的目标环节
     */
    private void transition2Node(Map process) {
        // 路径
        ((Map<String, XmlTransition>) process.transitions).each { k, v ->
            String nodeId = generateId(v.to, v.toName)
            v.toNode = process.nodes[nodeId]
            if (!v.toNode && nodeId.equals(process.end.id)) v.toNode = process.end
        }
    }

    private String generateId(String id, String name) {
        if (id) {
            return id
        } else if (name) {
            return new String(Base64Encode(name.getBytes("UTF-8"))).trim()
        } else {
            return null
        }
    }

    /** 批量添加属性 */
    private void addProps(bean, groovy.util.Node n, List<String> names) {
        for (String name in names) {
            String val = null
            // 节点属性
            if (name.indexOf('::') > -1) {
                String[] strs = name.split('::')
                name = strs[0]
                strs.reverseEach {
                    if (n.attribute(it) != null) val = n.attribute(it)
                }
            } else {
                val = n.attribute(name)
            }
            // 扩展属性：优先
            n.'bpmn:extensionElements'.'camunda:properties'.'camunda:property'.findAll { it.attribute('name') == name }.each { element ->
                val = element.attribute('value')
            }
            bean[name] = val;
        }
    }

    /**
     * 功能：编码byte[]
     *
     * @param data 源
     * @return char[]
     */
    private char[] Base64Encode(byte[] data) {
        char[] out = new char[((data.length + 2) / 3) * 4];
        for (int i = 0, index = 0; i < data.length; i += 3, index += 4) {
            boolean quad = false;
            boolean trip = false;

            int val = (0xFF & (int) data[i]);
            val <<= 8;
            if ((i + 1) < data.length) {
                val |= (0xFF & (int) data[i + 1]);
                trip = true;
            }
            val <<= 8;
            if ((i + 2) < data.length) {
                val |= (0xFF & (int) data[i + 2]);
                quad = true;
            }
            out[index + 3] = alphabet[(quad ? (val & 0x3F) : 64)];
            val >>= 6;
            out[index + 2] = alphabet[(trip ? (val & 0x3F) : 64)];
            val >>= 6;
            out[index + 1] = alphabet[val & 0x3F];
            val >>= 6;
            out[index + 0] = alphabet[val & 0x3F];
        }
        return out;
    }
    private static char[] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".toCharArray();
}